Hypercerts as a semi-fungible token (ERC1155)
為了使代幣可識別、可追蹤和可轉讓,hypercerts 被表示為 ERC-1155 代幣。ERC-1155 標準使得單個部署的合約能夠存儲許多 hypercerts,從而在單個命名空間內更簡單地創建、轉移、拆分和合併 hypercerts。作為一種半可替代代幣,每個獨特的代幣代表了一個 hypercert 的所有權的一部分。然後,hypercerts 被表示為一組代幣,其中總所有權總和為 100%。為了方便識別一個代幣屬於哪個 hypercert,我們利用 256 位代幣 ID 的前 128 位來識別 hypercert。同一 hypercert 群組內的所有代幣應共享相同的 ERC-1155 元數據。
舉例說明,假設TokenID有2個Bytes,其中第一個byte表示超證ID,最後一個byte表示所有權的比例。Alice可以創建一個新的超證token 0x2301,代表超證0x23的100%所有權。如果Alice想將20%轉讓給Bob,她可以通過鑄造Token: 0x2302並將價值的20%轉移到該token上來執行拆分(SPLIT)操作,這樣Token: 0x2301和0x2302分別代表超證0x23的80%和20%所有權。然後Alice將Token:0x2302轉給BOB。同樣地,他們可以將這兩個Token合併(MERGE)在一起,重新形成代表100%所有權的Token。在這種情況下,0x2301的價值將轉移到0x2302,然後0x2301將被銷毀。
/**
* AllowAll = Unrestricted
* DisallowAll = Transfers disabled after minting
* FromCreatorOnly = Only the original creator can transfer
*/
/// @dev Transfer restriction policies on hypercerts
enum TransferRestrictions {
AllowAll,
DisallowAll,
FromCreatorOnly
}
不同的mint方法
// Mint a semi-fungible token for the impact claim referenced via `uri`
function mintClaim(
address account,
uint256 units,
string memory _uri,
TransferRestrictions restrictions
) external override whenNotPaused {
// This enables us to release this restriction in the future
if (msg.sender != account) revert Errors.NotAllowed();
uint256 claimID = _mintNewTypeWithToken(account, units, _uri);
typeRestrictions[claimID] = restrictions;
emit ClaimStored(claimID, _uri, units);
}
// Mint semi-fungible tokens for the impact claim referenced via `uri`
function mintClaimWithFractions(
address account,
uint256 units,
uint256[] calldata fractions,
string memory _uri,
TransferRestrictions restrictions
) external override whenNotPaused {
// This enables us to release this restriction in the future
if (msg.sender != account) revert Errors.NotAllowed();
//Using sum to compare units and fractions (sanity check)
if (_getSum(fractions) != units) revert Errors.Invalid();
uint256 claimID = _mintNewTypeWithTokens(account, fractions, _uri);
typeRestrictions[claimID] = restrictions;
emit ClaimStored(claimID, _uri, units);
}
//Mint a semi-fungible token representing a fraction of the claim
/// @dev Calls AllowlistMinter to verify `proof`.
/// @dev Mints the `amount` of units for the hypercert stored under `claimID`
function mintClaimFromAllowlist(
address account,
bytes32[] calldata proof,
uint256 claimID,
uint256 units
) external whenNotPaused {
_processClaim(proof, claimID, units);
_mintToken(account, claimID, units);
}
Allowlist明天會看到
/// @notice Register a claim and the whitelist for minting token(s) belonging to that claim
/// @dev Calls SemiFungible1155 to store the claim referenced in `uri` with amount of `units`
/// @dev Calls AllowlistMinter to store the `merkleRoot` as proof to authorize claims
function createAllowlist(
address account,
uint256 units,
bytes32 merkleRoot,
string memory _uri,
TransferRestrictions restrictions
) external whenNotPaused {
uint256 claimID = _createTokenType(account, units, _uri);
_createAllowlist(claimID, merkleRoot, units);
typeRestrictions[claimID] = restrictions;
emit ClaimStored(claimID, _uri, units);
}
沿用了ERC1155Upgradeable、ERC1155BurnableUpgradeable,及ERC1155URIStorageUpgradeable
/// @dev Bitmask used to expose only upper 128 bits of uint256
uint256 internal constant TYPE_MASK = type(uint256).max << 128;
/// @dev Bitmask used to expose only lower 128 bits of uint256
uint256 internal constant NF_INDEX_MASK = type(uint256).max >> 128;
uint256 internal constant FRACTION_LIMIT = 253;
TokenID分別對應到現有owners及Creators的mapping(可作為Transfer限制的依據)
/// METADATA
/// @dev see { openzeppelin-contracts-upgradeable/token/ERC1155/extensions/ERC1155URIStorageUpgradeable.sol }
/// @dev Always returns the URI for the basetype so that it's managed in one place.
function uri(
uint256 tokenID
) public view virtual override(ERC1155Upgradeable, ERC1155URIStorageUpgradeable) returns (string memory _uri) {
// All tokens share the same metadata at the moment
_uri = ERC1155URIStorageUpgradeable.uri(getBaseType(tokenID));
}
/// @dev Split the units of `_tokenID` owned by `account` across `_values`
/// @dev `_values` must sum to total `units` held at `_tokenID`
function _splitTokenUnits(address _account, uint256 _tokenID, uint256[] calldata _values) internal {
if (_values.length > FRACTION_LIMIT || _values.length < 2) revert Errors.ArraySize();
if (tokenValues[_tokenID] != _getSum(_values)) revert Errors.NotAllowed();
// Current token
uint256 _typeID = getBaseType(_tokenID);
uint256 valueLeft = tokenValues[_tokenID];
// Prepare batch processing, we want to skip the first entry
uint256 len = _values.length - 1;
uint256[] memory typeIDs = new uint256[](len);
uint256[] memory fromIDs = new uint256[](len);
uint256[] memory toIDs = new uint256[](len);
uint256[] memory amounts = new uint256[](len);
uint256[] memory values = new uint256[](len);
{
uint256[] memory _valuesCache = _values;
uint256 swapValue = _valuesCache[len];
_valuesCache[len] = _valuesCache[0];
_valuesCache[0] = swapValue;
for (uint256 i; i < len; ) {
_notMaxItem(maxIndex[_typeID]);
typeIDs[i] = _typeID;
fromIDs[i] = _tokenID;
toIDs[i] = _typeID + ++maxIndex[_typeID];
amounts[i] = 1;
values[i] = _valuesCache[i];
unchecked {
++i;
}
}
}
_beforeUnitTransfer(_msgSender(), _account, fromIDs, toIDs, values, "");
for (uint256 i; i < len; ) {
valueLeft -= values[i];
tokenValues[toIDs[i]] = values[i];
unchecked {
++i;
}
}
tokenValues[_tokenID] = valueLeft;
_mintBatch(_account, toIDs, amounts, "");
emit BatchValueTransfer(typeIDs, fromIDs, toIDs, values);
}
/// @dev Merge the units of `_fractionIDs`.
/// @dev Base type of `_fractionIDs` must be identical for all tokens.
function _mergeTokensUnits(address _account, uint256[] memory _fractionIDs) internal {
if (_fractionIDs.length > FRACTION_LIMIT || _fractionIDs.length < 2) {
revert Errors.ArraySize();
}
uint256 len = _fractionIDs.length - 1;
uint256 target = _fractionIDs[len];
uint256 _totalValue;
uint256[] memory fromIDs = new uint256[](len);
uint256[] memory toIDs = new uint256[](len);
uint256[] memory values = new uint256[](len);
uint256[] memory amounts = new uint256[](len);
{
for (uint256 i; i < len; ) {
uint256 _fractionID = _fractionIDs[i];
fromIDs[i] = _fractionID;
toIDs[i] = target;
amounts[i] = 1;
values[i] = tokenValues[_fractionID];
unchecked {
++i;
}
}
}
_beforeUnitTransfer(_msgSender(), _account, fromIDs, toIDs, values, "");
for (uint256 i; i < len; ) {
_totalValue += values[i];
delete tokenValues[fromIDs[i]];
unchecked {
++i;
}
}
tokenValues[target] += _totalValue;
_burnBatch(_account, fromIDs, amounts);
}